﻿
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;

namespace FasterWPF
{
    #region Cata Code Reference from Bart De Smet - for inspiration
    //Cata from Bart De Smet - what would we need for a FoldLeft or Sum?  Again merely different ways of filtering?
    //perhaps this does NOT need to be implemented in IParent?!?

    //        public static R Cata<T, R>(this Func<Func<Option<T>>> source, R seed, Func<R, T, R> f)
    //{
    //    var e = source();

    //    Option<T>.Some value;
    //    R result = seed;
    //    while ((value = e() as Option<T>.Some) != null)
    //    {
    //        result = f(result, value.Value);
    //    }

    //    return result;
    //}

    //public static R Cata<T, R>(this Action<Action<Option<T>>> source, R seed, Func<R, T, R> f)
    //{
    //    R result = seed;

    //    bool end = false;
    //    source(x =>
    //    {
    //        if (x is Option<T>.Some && !end)
    //            result = f(result, x.Value);
    //        else
    //            end = true; // or break using exception
    //    });

    //    return result;
    //}
    #endregion

    #region ISortArgs

    public class ListViewSort : FasterWPF.CommonExt.ISortArgs
    {
        public ListViewSort(CommonExt.SortDataType sortDataType)
        {
            SortDataType = sortDataType;
        }

        public ListViewSort(CommonExt.SortDataType sortDataType, CommonExt.SortDataType sortDataType2)
        {
            SortDataType = sortDataType;
            SortDataType2 = sortDataType2;
        }

        public CommonExt.SortDataType SortDataType
        {
            get;
            set;
        }

        public CommonExt.SortDataType SortDataType2
        {
            get;
            set;
        }
    }

    #endregion

    public class ListViewArgs
    {        
    }

    public class ListViewParent : FasterWPF.IParent, FasterWPF.IScrollViewerStrategy, ICanSort, ICanSelect, ICanFilter
    {
        //Ana
        public object Generate<T>(object chain, T args, Action<ContentControl> addUIElementsAction)
        {
            throw new NotImplementedException();
        }

        #region IParent

        public object Add<T>(object chain, string key, T args, Border itemToAdd)
        {
            //args is always NULL for ListViewComposite

            ((ListView)chain).GetSetting<ListView, ObservableCollection<Border>>("ObsCol").Add(itemToAdd);

            //set these here to allow overriding the default values
            ScrollViewerExt.SetCanContentScroll<FrameworkElement>((FrameworkElement)chain);
            //ScrollViewerExt.SetVerticalPageUpButtonDelayAndInterval<FrameworkElement>((FrameworkElement)chain);

            var collection = ((ListView)chain).GetSetting<ListView, ObservableCollection<Border>>("ObsCol");
            
            if (((ListView)chain).ContainsSetting("GroupItemConverter") && 
                ((ListView)chain).ContainsSetting("GroupItemFrameworkElementFactory"))
            {                        
                //Allow Group Header
                if (((ListView)chain).ContainsSetting("GroupExpanderFrameworkElementFactory"))
                {
                    CollectionViewSource cvs = new CollectionViewSource();
                    cvs.GroupDescriptions.Add(new PropertyGroupDescription(null, new XYConverter()));
                    cvs.Source = collection;

                    Binding b = new Binding();
                    b.Source = cvs;

                    //b.NotifyOnSourceUpdated = true;
                    //b.NotifyOnTargetUpdated = true;

                    BindingOperations.SetBinding(
                       ((ListView)chain), ListView.ItemsSourceProperty, b);

                    //only add once
                    if (((ListView)chain).GroupStyle.Count == 0)
                    {
                        DataTemplate templateForLabel = new DataTemplate(typeof(GroupItem));

                        DataTemplate templateForItemsControl = new DataTemplate(typeof(ContentControl));
                        ControlTemplate templateForExpander = new ControlTemplate(typeof(GroupItem));

                        FrameworkElementFactory factoryExpander = ((ListView)chain).GetSetting<ListView, FrameworkElementFactory>("GroupExpanderFrameworkElementFactory");                      
                        
                        MultiBinding mb = new MultiBinding();
                        mb.Bindings.Add(new Binding("Name"));
                        mb.Bindings.Add(new Binding("ItemCount"));
                        GroupLabelConverter glcon = new GroupLabelConverter();
                        mb.Converter = glcon;

                        FrameworkElementFactory factoryLabelName = ((ListView)chain).GetSetting<ListView, FrameworkElementFactory>("GroupItemFrameworkElementFactory");
                        templateForLabel.VisualTree = factoryLabelName;
                        
                        FrameworkElementFactory factoryPresenter = new FrameworkElementFactory(typeof(ItemsControl));
                        factoryPresenter.SetValue(ItemsControl.ItemsSourceProperty, new Binding() { Path = new PropertyPath("Content"), RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Expander), 1) });
                        templateForItemsControl.VisualTree = factoryPresenter;

                        factoryExpander.SetValue(Expander.HeaderTemplateProperty, templateForLabel);
                        factoryExpander.SetValue(Expander.ContentTemplateProperty, templateForItemsControl);
                        factoryExpander.SetValue(Expander.TagProperty, mb);
                        factoryExpander.SetValue(Expander.ContentProperty, new Binding() { Path = new PropertyPath("Items") });

                        Style myStyle = new Style(typeof(GroupItem));
                        Setter setter = new Setter();
                        setter.Property = GroupItem.MarginProperty;
                        setter.Value = new Thickness(0, 0, 0, 5);
                        myStyle.Setters.Add(setter);

                        Setter setterTemplate = new Setter();
                        setterTemplate.Property = GroupItem.TemplateProperty;
                        setterTemplate.Value = templateForExpander;
                        myStyle.Setters.Add(setterTemplate);
                        
                        templateForExpander.VisualTree = factoryExpander;

                        GroupStyle groupStyle = new GroupStyle();

                        groupStyle.ContainerStyle = myStyle;

                        ((ListView)chain).GroupStyle.Add(groupStyle);
                    }
                }
                else
                {
                    CollectionViewSource cvs = new CollectionViewSource();
                    cvs.GroupDescriptions.Add(new PropertyGroupDescription(null, new XYConverter()));
                    cvs.Source = collection;

                    Binding b = new Binding() { Source = cvs };
                    BindingOperations.SetBinding(((ListView)chain), ListView.ItemsSourceProperty, b);

                    if (((ListView)chain).GroupStyle.Count == 0) //only add once
                    {
                        DataTemplate template = new DataTemplate(typeof(GroupItem));

                        FrameworkElementFactory factoryTextBlockName = ((ListView)chain).GetSetting<ListView, FrameworkElementFactory>("GroupItemFrameworkElementFactory");

                        template.VisualTree = factoryTextBlockName;
                        
                        GroupStyle groupStyle = new GroupStyle();
                        groupStyle.HeaderTemplate = template;

                        ((ListView)chain).GroupStyle.Add(groupStyle);
                    }
                }
            }
            else
            {
                if (((ListView)chain).ItemsSource != collection)
                {
                    ((ListView)chain).ItemsSource = collection;
                }
            }

            return chain;
        }

        public void Remove(object chain, string key, Border itemToRemove)
        {
            ((ListView)chain).GetSetting<ListView, ObservableCollection<Border>>("ObsCol").Remove(itemToRemove); //remove from ListView

            IComposite ic = (IComposite)(((ListView)chain).GetIComposite<ListView>());
            ic.RemoveFromChain(itemToRemove);
        }

        public Dictionary<string, object> RetrieveDictionary(object chain)
        {
            return ((ListView)chain).GetCompositeLookupDictionary<ListView>();
        }
        
        //Cata

        //ADD A WINDOWComposite, as the top-level Root Container?!?! then for Selectors you may always recurse down from here.
        //For SELECTORS, consider that this is merely QUERYING. Your hard-filtering that REMOVES is different. That's just another RemoveByKey just with a Predicate :-)

        //TIE into SELECTORS API - should this RetrieveCollection have multiple overloads for retrieve by row/col, by row, by col?
        public List<Border> RetrieveCollection(object chain)
        {
            ObservableCollection<Border> ListViewItems = ((ListView)chain).GetSetting<ListView, ObservableCollection<Border>>("ObsCol");
            return ListViewItems.ToList<Border>();
        }
        
        #endregion

        #region ICanSort

        public void ClearCollection(object chain)
        {
            ObservableCollection<Border> ListViewItems = ((ListView)chain).GetSetting<ListView, ObservableCollection<Border>>("ObsCol");
            ListViewItems.Clear();
        }

        public void BeforeSortCollection<T>(object chain, object args, int row, int column, FasterWPF.CommonExt.SortOrder sortOrder, CommonExt.SortControlType sortControlType)
                  where T : FrameworkElement
        {
            var orderedKeys = ((ListView)chain).GetOrderedKeyList();
            var rows = ((ListView)chain).GetCompositeLookupDictionary();
            Dictionary<string, object> ChildLookupDict = ((ListView)chain).GetChildLookupDictionary();

            IParent currentComposite = ((ListView)chain).GetSetting<ListView, IParent>("IParent");
            List<Border> ListViewItems = currentComposite.RetrieveCollection(chain);

            if (ListViewItems != null && ListViewItems.Count > 0)
            {
                ((ICanSort)currentComposite).ClearCollection(chain);

                orderedKeys.Clear();

                IEnumerable<string> items = null;

                if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType) == FasterWPF.CommonExt.SortDataType.DateTime)
                {
                    items = CommonInternalExt.PerformSortOfKeysLevelOneDateTime<T>(row, column, sortOrder, ChildLookupDict, items, sortControlType);
                }
                else if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType) == FasterWPF.CommonExt.SortDataType.Decimal)
                {
                    items = CommonInternalExt.PerformSortOfKeysLevelOneDecimal<T>(row, column, sortOrder, ChildLookupDict, items, sortControlType);
                }
                else if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType) == FasterWPF.CommonExt.SortDataType.Integer)
                {
                    items = CommonInternalExt.PerformSortOfKeysLevelOneInteger<T>(row, column, sortOrder, ChildLookupDict, items, sortControlType);
                }
                else
                {
                    //Default to String
                    items = CommonInternalExt.PerformSortOfKeysLevelOne<T>(row, column, sortOrder, ChildLookupDict, items, sortControlType);
                }
        
                ObservableCollection<Border> obsListViewItems = ((ListView)chain).GetSetting<ListView, ObservableCollection<Border>>("ObsCol");

                //Add sorted items back to the ListView
                foreach (string pair in items)
                {
                    obsListViewItems.Add((Border)rows[pair]);
                    orderedKeys.Add(pair);
                }
            }                    
        }

        public void BeforeSortCollection1Then2<T>(object chain, object args, int row, int column, int row2, int column2, FasterWPF.CommonExt.SortOrder sortOrder, FasterWPF.CommonExt.SortOrder sortOrder2, CommonExt.SortControlType sortControlType)
                  where T : FrameworkElement
        {
            var orderedKeys = ((ListView)chain).GetOrderedKeyList();
            var rows = ((ListView)chain).GetCompositeLookupDictionary();

            IParent currentComposite = ((ListView)chain).GetSetting<ListView, IParent>("IParent");
            List<Border> ListViewItems = currentComposite.RetrieveCollection(chain);

            //if there are items to sort
            if (ListViewItems != null && ListViewItems.Count > 0)
            {
                ((ICanSort)currentComposite).ClearCollection(chain);

                Dictionary<string, Tuple<object, object>> sourceToSortOn = new Dictionary<string, Tuple<object, object>>();

                string defaultNullValue = null;
                string defaultNullValue2 = null;

                if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType) == FasterWPF.CommonExt.SortDataType.DateTime)
                {
                    defaultNullValue = "1/1/1900";
                }
                else if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType) == FasterWPF.CommonExt.SortDataType.Decimal)
                {
                    defaultNullValue = "0.00";
                }
                else if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType) == FasterWPF.CommonExt.SortDataType.Integer)
                {
                    defaultNullValue = "0";
                }
                else
                {
                    defaultNullValue = "";
                }

                if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType2) == FasterWPF.CommonExt.SortDataType.DateTime)
                {
                    defaultNullValue2 = "1/1/1900";
                }
                else if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType2) == FasterWPF.CommonExt.SortDataType.Decimal)
                {
                    defaultNullValue2 = "0.00";
                }
                else if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType2) == FasterWPF.CommonExt.SortDataType.Integer)
                {
                    defaultNullValue2 = "0";
                }
                else
                {
                    defaultNullValue2 = "";
                }

                CommonInternalExt.SetDefaultsForTextElementsAtRowCol<ListView>(chain, row, column, row2, column2, sortControlType, orderedKeys, sourceToSortOn, defaultNullValue, defaultNullValue2);
                
                orderedKeys.Clear();
                IOrderedEnumerable<KeyValuePair<string, Tuple<object, object>>> items = null;

                Type dataType1 = null;
                Type dataType2 = null;

                if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType) == FasterWPF.CommonExt.SortDataType.DateTime)
                {
                    dataType1 = typeof(DateTime);
                }
                else if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType) == FasterWPF.CommonExt.SortDataType.Decimal)
                {
                    dataType1 = typeof(Decimal);
                }
                else if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType) == FasterWPF.CommonExt.SortDataType.Integer)
                {
                    dataType1 = typeof(Int32);
                }
                else
                {
                    dataType1 = typeof(String);
                }

                if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType2) == FasterWPF.CommonExt.SortDataType.DateTime)
                {
                    dataType2 = typeof(DateTime);
                }
                else if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType2) == FasterWPF.CommonExt.SortDataType.Decimal)
                {
                    dataType2 = typeof(Decimal);
                }
                else if (args is FasterWPF.CommonExt.ISortArgs && (((FasterWPF.CommonExt.ISortArgs)args).SortDataType2) == FasterWPF.CommonExt.SortDataType.Integer)
                {
                    dataType2 = typeof(Int32);
                }
                else
                {
                    dataType2 = typeof(String);
                }

                items = CommonInternalExt.PerformSortOfKeysLevelTwo(chain, sortOrder, sortOrder2, sourceToSortOn, items, dataType1, dataType2);
                
                ObservableCollection<Border> obsListViewItems = ((ListView)chain).GetSetting<ListView, ObservableCollection<Border>>("ObsCol");

                //Add sorted items back to the ListView
                foreach (KeyValuePair<string, Tuple<object, object>> pair in items)
                {
                    obsListViewItems.Add((Border)rows[pair.Key]);
                    orderedKeys.Add(pair.Key);
                }
            }
        }

        public void AfterSortCollection<T>(object chain)
                  where T : FrameworkElement
        { 
            //not necessary for ListView
        }

        public void InnerSortCollection(object chain, int row, int column, string key, ref int counter, Border itemToReAdd)
        {
            //not necessary for ListView
        }

        #endregion

        #region ICanSelect

        //This is likely necessary in IParent. It is likely unique across many different elements.
        public void SetSelected(object chain, Border itemToSetAsSelected)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region ICanFilter

        public T FilterByText<T>(T chain, int row, int column, Predicate<string> filterPredicate)
              where T : FrameworkElement
        {
            var orderedKeys = chain.GetOrderedKeyList();
            var rows = chain.GetCompositeLookupDictionary();

            IParent currentComposite = chain.GetSetting<T, IParent>("IParent");
            List<Border> ListViewItems = currentComposite.RetrieveCollection(chain);

            //if there are items to filter
            if (ListViewItems != null && ListViewItems.Count > 0)
            {
                List<String> orderedKeyList = chain.GetOrderedKeyList();
                Dictionary<string, string> sourceToFilterOn = new Dictionary<string, string>();

                foreach (string key in orderedKeyList)
                {
                    TextBlock value = (TextBlock)((Dictionary<string, object>)((Dictionary<string, object>)chain.Tag)["ChildLookup"])[key +
    "_" + row + "_" + column];
                    sourceToFilterOn.Add(key, value.GetText());
                }

                IEnumerable<KeyValuePair<string, string>> items = null;

                //Find all the items that do NOT satisfy the filter
                items = from pair in sourceToFilterOn
                        where !filterPredicate(pair.Value)
                        select pair;

                foreach (KeyValuePair<string, string> pair in items)
                {
                    chain.RemoveByKey(pair.Key);
                    orderedKeys.Remove(pair.Key);
                }
            }
            
            return chain;
        }

        public T FilterByText1And2<T>(T chain, int row, int column, int row2, int column2, Predicate<Tuple<string, string>> filterPredicate)
       where T : FrameworkElement
        {
            var orderedKeys = chain.GetOrderedKeyList();
            var rows = chain.GetCompositeLookupDictionary();

            IParent currentComposite = chain.GetSetting<T, IParent>("IParent");
            List<Border> ListViewItems = currentComposite.RetrieveCollection(chain);

            //if there are items to filter
            if (ListViewItems != null && ListViewItems.Count > 0)
            {
                List<String> orderedKeyList = chain.GetOrderedKeyList();
                Dictionary<string, Tuple<string, string>> sourceToFilterOn = new Dictionary<string, Tuple<string, string>>();

                foreach (string key in orderedKeyList)
                {
                    TextBlock value = (TextBlock)((Dictionary<string, object>)((Dictionary<string, object>)chain.Tag)["ChildLookup"])[key +
    "_" + row + "_" + column];

                    TextBlock value2 = (TextBlock)((Dictionary<string, object>)((Dictionary<string, object>)chain.Tag)["ChildLookup"])[key +
    "_" + row2 + "_" + column2];
                    sourceToFilterOn.Add(key, new Tuple<string, string>(value.GetText(), value2.GetText()));
                }

                IEnumerable<KeyValuePair<string, Tuple<string, string>>> items = null;

                //Find all the items that do NOT satisfy the filter
                items = from pair in sourceToFilterOn
                        where !filterPredicate(pair.Value)
                        select pair;

                foreach (KeyValuePair<string, Tuple<string, string>> pair in items)
                {
                    chain.RemoveByKey(pair.Key);
                    orderedKeys.Remove(pair.Key);
                }
            }

            return chain;
        }

        public T FilterByText1And2And3<T>(T chain, int row, int column, int row2, int column2, int row3, int column3, Predicate<Tuple<string, string, string>> filterPredicate)
       where T : FrameworkElement
        {
            var orderedKeys = chain.GetOrderedKeyList();
            var rows = chain.GetCompositeLookupDictionary();

            IParent currentComposite = chain.GetSetting<T, IParent>("IParent");
            List<Border> ListViewItems = currentComposite.RetrieveCollection(chain);

            //if there are items to filter
            if (ListViewItems != null && ListViewItems.Count > 0)
            {
                List<String> orderedKeyList = chain.GetOrderedKeyList();
                Dictionary<string, Tuple<string, string, string>> sourceToFilterOn = new Dictionary<string, Tuple<string, string, string>>();

                foreach (string key in orderedKeyList)
                {
                    TextBlock value = (TextBlock)((Dictionary<string, object>)((Dictionary<string, object>)chain.Tag)["ChildLookup"])[key +
    "_" + row + "_" + column];

                    TextBlock value2 = (TextBlock)((Dictionary<string, object>)((Dictionary<string, object>)chain.Tag)["ChildLookup"])[key +
    "_" + row2 + "_" + column2];

                    TextBlock value3 = (TextBlock)((Dictionary<string, object>)((Dictionary<string, object>)chain.Tag)["ChildLookup"])[key +
"_" + row3 + "_" + column3];

                    sourceToFilterOn.Add(key, new Tuple<string, string, string>(value.GetText(), value2.GetText(), value3.GetText()));
                }

                IEnumerable<KeyValuePair<string, Tuple<string, string, string>>> items = null;

                //Find all the items that do NOT satisfy the filter
                items = from pair in sourceToFilterOn
                        where !filterPredicate(pair.Value)
                        select pair;

                foreach (KeyValuePair<string, Tuple<string, string, string>> pair in items)
                {
                    chain.RemoveByKey(pair.Key);
                    orderedKeys.Remove(pair.Key);
                }
            }

            return chain;
        }

        #endregion

        #region IScrollViewerStrategy

        public ScrollViewer GetScrollViewer(object chain)
        {
            ListView lbx = (ListView)chain;
            var builtCheck = lbx.ApplyTemplate();
            Border bd = (Border)lbx.Template.FindName("Bd", lbx);

            var settings = lbx.GetSettingsDictionary();

            if (settings.Keys.Contains("ScrollViewerPadding"))
            {
                bd.Padding = (Thickness)settings["ScrollViewerPadding"];
            }
            else
            {
                bd.Padding = new Thickness(0); //default to zero
            }
            ScrollViewer svw = bd.Child.Cast<ScrollViewer>();

            return svw;
        }

        public T GetParentContainerFromScrollViewerThumb<T>(System.Windows.Controls.Primitives.Thumb myThumb)
        {
            T source = ((Border)((ScrollViewer)
                ((Grid)
                ((ScrollBar)myThumb.TemplatedParent).Parent).TemplatedParent
                ).Parent).TemplatedParent.Cast<T>();
            return source;
        }

        #endregion
    }
}
